/* We use the POSIX regex functions to process regular expressions.
 * Type 'man regex' for more information about POSIX regex functions.
 */
#include "temu.h"
#include "expr.h"
#include "debug.h"
#include <regex.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>

extern int name_to_index(char *name);

#define REG_STRING "\\$zero|\\$ra|\\$tp|\\$sp|\\$a[0-7]|\\$t[0-8]|\\$x|\\$fp|\\$s[0-8]|\\$pc"
#define NR_REGEX (sizeof(rules) / sizeof(rules[0]) )

enum {
    SPACE = 0,
    PLUS,
    MINUS,
    NEGATIVE,
    MULTIPLE,
    DIVIDE,
    LEFT_BRACKET,
    RIGHT_BRACKET,
    NUMBER,
    EQUAL,
    UNEQUAL,
    HEX,
    REG,
    L_AND,
    L_OR,
    L_NOT,
    DEREF,
    /* TODO: Add more token types */
};

static struct rule {
    char *regex;
    int token_type;
} rules[] = {
        /* TODO: Add more rules. Pay attention to the precedence level of different rules.*/
        {REG_STRING,       REG},
        {"0x[0-9a-fA-F]+", HEX},
        {"[0-9]+",         NUMBER},
        {"==",             EQUAL},
        {"!=",             UNEQUAL},
        {"&&",             L_AND},
        {"\\|\\|",         L_OR},
        {"!",              L_NOT},
        {" +",             SPACE},
        {"\\+",            PLUS},
        {"-",              MINUS},
        {"\\*",            MULTIPLE},
        {"\\/",            DIVIDE},
        {"\\(",            LEFT_BRACKET},
        {"\\)",            RIGHT_BRACKET},
};
char *param_rule = "-[a-zA-Z]+";

static regex_t re[NR_REGEX];
static regex_t param_re;

int eval(int p, int q, int *out);

bool check_parentheses(int p, int q);

int getOP(int p, int q, int *type);

/* Rules are used for many times.
 * Therefore, we compile them only once before any usage.
 */
void init_regex() {
    int i;
    char error_msg[128];
    int ret;

    for (i = 0; i < NR_REGEX; i++) {
        ret = regcomp(&re[i], rules[i].regex, REG_EXTENDED);
        if (ret != 0) {
            regerror(ret, &re[i], error_msg, 128);
            Assert(ret == 0, "regex compilation failed: %s\n%s", error_msg, rules[i].regex);
        }
    }

    ret = regcomp(&param_re, param_rule, REG_EXTENDED);
    if (ret != 0) {
        regerror(ret, &param_re, error_msg, 128);
        Assert(ret == 0, "regex compilation failed: %s\n%s", error_msg, param_rule);
    }
}

Param params[EXP_MAX_PARAMS];
int nr_param;
#define TMP_SIZE 1024

static bool param_make_token(char *e) {
    int position = 0;
    regmatch_t p_match;
    nr_param = 0;
    char *tmp;
    tmp = malloc(TMP_SIZE);
    memset(tmp, 0, TMP_SIZE);

    while (e[position] != '\0') {
        if (nr_param >= EXP_MAX_TOKENS) {
            printf("Params: no more token allowed!\n");
            return false;
        }

        if (regexec(&param_re, e + position, 1, &p_match, 0) == 0 && p_match.rm_so == 0) {
            char *substr_start = e + position;
            int substr_len = (int) p_match.rm_eo;
            strncpy(params[nr_param].str, substr_start, substr_len);

            strncpy(tmp, e, position);
            printf("1:%s | %s\n", tmp, e);
            if (position + substr_len + 1 < strlen(e)) {
                strcat(tmp, e + position + substr_len + 1);
                printf("2:%s | %s\n", tmp, e);
            }
            strcpy(e, tmp);
            printf("3:%s | %s\n", tmp, e);

            memset(tmp, 0, TMP_SIZE);
            nr_param++;
        } else ++position;
    }

    free(tmp);
    return true;
}

void get_param(char *e, Params *pars) {
    if(e==NULL) {
        (*pars).params = NULL;
        (*pars).count = 0;
        return;
    }
    if (!param_make_token(e)) return;
    (*pars).params = params;
    (*pars).count = nr_param;
}

Token tokens[EXP_MAX_TOKENS];
int nr_token;

static bool make_token(char *e) {
    int position = 0;
    int i;
    regmatch_t p_match;

    nr_token = 0;

    while (e[position] != '\0') {
        /* Try all rules one by one. */
        if (nr_token >= EXP_MAX_TOKENS) {
            printf("Tokens: no more token allowed!\n");
            return false;
        }
        for (i = 0; i < NR_REGEX; i++) {
            if (regexec(&re[i], e + position, 1, &p_match, 0) == 0 && p_match.rm_so == 0) {
                char *substr_start = e + position;
                int substr_len = (int) p_match.rm_eo;

                Log("match rules[%d] = \"%s\" at position %d with len %d: %.*s", i, rules[i].regex, position,
                    substr_len, substr_len, substr_start);
                position += substr_len;

                /* TODO: Now a new token is recognized with rules[i]. Add codes
                 * to record the token in the array `tokens'. For certain types
                 * of tokens, some extra actions should be performed.
                 */

                tokens[nr_token].type = rules[i].token_type;
                switch (rules[i].token_type) {
                    case SPACE:
                        nr_token--;
                        break;
                    case PLUS:
                        strcpy(tokens[nr_token].str, "+");
                        break;
                    case MINUS:
                        strcpy(tokens[nr_token].str, "-");
                        break;
                    case MULTIPLE:
                        strcpy(tokens[nr_token].str, "*");
                        break;
                    case DIVIDE:
                        strcpy(tokens[nr_token].str, "/");
                        break;
                    case LEFT_BRACKET:
                        strcpy(tokens[nr_token].str, "(");
                        break;
                    case RIGHT_BRACKET:
                        strcpy(tokens[nr_token].str, ")");
                        break;
                    case EQUAL:
                        strcpy(tokens[nr_token].str, "==");
                        break;
                    case UNEQUAL:
                        strcpy(tokens[nr_token].str, "!=");
                        break;
                    case L_AND:
                        strcpy(tokens[nr_token].str, "&&");
                        break;
                    case L_OR:
                        strcpy(tokens[nr_token].str, "||");
                        break;
                    case L_NOT:
                        strcpy(tokens[nr_token].str, "!");
                        break;
                    case NUMBER:
                    case HEX:
                    case REG:
                        strncpy(tokens[nr_token].str, substr_start, substr_len);
                        break;
                }
                nr_token++;

                break;
            }
        }

        if (i == NR_REGEX) {
            printf("no match at position %d\n%s\n%*.s^\n", position, e, position, "");
            return false;
        }
    }

    return true;
}

uint32_t expr(char *e, bool *success) {
    int i, err = 0, ans;
    if (!make_token(e)) {
        *success = false;
        return 0;
    }

    /* 将乘法转换为解引用 */
    for (i = 0; i < nr_token; ++i) {
        if (tokens[i].type == MULTIPLE &&
            (i == 0 || tokens[i - 1].type == PLUS || tokens[i - 1].type == MINUS || tokens[i - 1].type == MULTIPLE ||
             tokens[i - 1].type == DIVIDE || tokens[i - 1].type == LEFT_BRACKET || tokens[i - 1].type == L_AND ||
             tokens[i - 1].type == L_OR || tokens[i - 1].type == L_NOT || tokens[i - 1].type == EQUAL ||
             tokens[i - 1].type == UNEQUAL)) {
            tokens[i].type = DEREF;
        }
    }
    for (i = 0; i < nr_token; ++i) {
        if (tokens[i].type == MINUS &&
            (i == 0 || tokens[i - 1].type == PLUS || tokens[i - 1].type == MINUS || tokens[i - 1].type == MULTIPLE ||
             tokens[i - 1].type == DIVIDE || tokens[i - 1].type == LEFT_BRACKET || tokens[i - 1].type == L_AND ||
             tokens[i - 1].type == L_OR || tokens[i - 1].type == L_NOT || tokens[i - 1].type == EQUAL ||
             tokens[i - 1].type == UNEQUAL)) {
            tokens[i].type = NEGATIVE;
        }
    }

    ans = eval(0, nr_token - 1, &err);
    *success = err != 1;
    for (i = 0; i < nr_token; ++i) {
        memset(tokens[i].str, 0, 32);
    }
    return ans;
}

int eval(int p, int q, int *out) {
//    printf(":%d,%d,out:%d\n", p, q, *out);
    char *end = "\0";
    int ans = 0, op, op_type, val1, val2;
    if (p > q) {
        ERR("Bad expression.");
    } else if (p == q) {
        if (tokens[p].type == NUMBER || tokens[p].type == HEX) {
            ans = (int) strtol(tokens[p].str, &end, 0);
        } else if (tokens[p].type == REG) {
            ans = (int) (0 == strcmp(tokens[p].str, "$pc") ? cpu.pc : cpu.gpr[name_to_index(tokens[p].str)]._32);
        }
        if (tokens[p].type == REG || *end == '\0') return ans;
        ERR("Illegal number token: [%s]", tokens[p].str);
    } else if (check_parentheses(p, q)) {
        return eval(p + 1, q - 1, out);
    } else {
        op = getOP(p, q, &op_type);
        if (op == p) {
            if (op_type == L_NOT) return !eval(op + 1, q, out);
            else if (op_type == NEGATIVE) return ~eval(op + 1, q, out) + 1;
            else if (op_type == DEREF) return (int) mem_read(eval(op + 1, q, out) & 0x7FFFFFFF, 4);
            else { ERR("Unsupported operator."); }
        }

        val1 = eval(p, op - 1, out);
        if (*out == 1) return 0;
        val2 = eval(op + 1, q, out);
        if (*out == 1) return 0;
        switch (op_type) {
            case PLUS:
                return val1 + val2;
            case MINUS:
                return val1 - val2;
            case MULTIPLE:
                return val1 * val2;
            case DIVIDE:
                if (val2 != 0) return val1 / val2;
                else { ERR("The dividend is zero.") }
            case L_AND:
                return val1 && val2;
            case L_OR:
                return val1 || val2;
            case EQUAL:
                return val1 == val2;
            case UNEQUAL:
                return val1 != val2;
            default:
            ERR("What did you input!")
        }
    }
}

bool check_parentheses(int p, int q) {
    int i, c = 0;
    if (tokens[p].type != LEFT_BRACKET || tokens[q].type != RIGHT_BRACKET) return false;

    for (i = p + 1; i < q; i++) {
        if (c < 0) return false;
        if (tokens[i].type == LEFT_BRACKET) c++;
        else if (tokens[i].type == RIGHT_BRACKET) c--;
    }
    return c == 0;
}

int getOP(int p, int q, int *type) {
    int i, ans = p, bracket_cnt = 0;
    uint32_t priority = -1;
    for (i = p; i < q; ++i) {
        for (; bracket_cnt > 0; ++i) {
            if (tokens[i].type == RIGHT_BRACKET) --bracket_cnt;
            else if (tokens[i].type == LEFT_BRACKET) ++bracket_cnt;
        }

        if (tokens[i].type == LEFT_BRACKET) {
            ++bracket_cnt;
            continue;
        }

        if (priority >= 10 && (tokens[i].type == L_AND || tokens[i].type == L_OR)) {
            ans = i;
            priority = 10;
            *type = tokens[i].type;
        }

        if (priority >= 50 && (tokens[i].type == L_NOT || tokens[i].type == DEREF || tokens[i].type == NEGATIVE)) {
            ans = i;
            priority = 50;
            *type = tokens[i].type;
        }

        if (priority >= 20 && (tokens[i].type == EQUAL || tokens[i].type == UNEQUAL)) {
            ans = i;
            priority = 20;
            *type = tokens[i].type;
        }

        if (priority >= 30 && (tokens[i].type == PLUS || tokens[i].type == MINUS)) {
            ans = i;
            priority = 30;
            *type = tokens[i].type;
        }

        if (priority >= 40 && (tokens[i].type == MULTIPLE || tokens[i].type == DIVIDE)) {
            ans = i;
            priority = 40;
            *type = tokens[i].type;
        }
    }
    return ans;
}